package com.mulong.common.web.handler;

import jakarta.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson2.JSON;
import com.mulong.common.domain.vo.BaseResult;
import com.mulong.common.exception.ErrorEnums;
import com.mulong.common.exception.MulongException;
import com.mulong.common.util.RequestContext;

import lombok.extern.slf4j.Slf4j;

/**
 * GlobalExceptionHandler
 * 
 * @author mulong
 * @date 2021-03-06 00:24:47
 */
@ConditionalOnProperty(value = "mulong.global.exception.handler.enabled", havingValue = "true", matchIfMissing = true)
@Slf4j
@ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandler {
    private static final String NOT_EMPTY = "不能为空";

    @ExceptionHandler({BindException.class})
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public BaseResult<?> bindExceptionHandler(BindException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        BaseResult<?> res = bindExceptionHandler(bindingResult);
        logResponse(res);
        return res;
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public BaseResult<?> bindExceptionHandler(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        BaseResult<?> res = bindExceptionHandler(bindingResult);
        logResponse(res);
        return res;
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public BaseResult<?> exceptionHandler(MulongException ex, HttpServletRequest request) {
        log.error(ex.getMessage(), ex);
        ErrorEnums error = ex.getError();
        BaseResult<?> res = BaseResult.failure(error.getCode(), error.getSvrMsg());
        logResponse(res);
        return res;
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public BaseResult<?> exceptionHandler(Exception ex, HttpServletRequest request) {
        log.error(ex.getMessage(), ex);
        if (StringUtils.containsIgnoreCase(ExceptionUtils.getRootCauseMessage(ex), "Broken pipe")) {
            return null;
        }
        ErrorEnums error = handle(ex);
        BaseResult<?> res = BaseResult.failure(error.getCode(), error.getSvrMsg());
        logResponse(res);
        return res;
    }

    private BaseResult<?> bindExceptionHandler(BindingResult bindingResult) {
        FieldError error = bindingResult.getFieldErrors().get(0);
        String msg = error.getDefaultMessage();
        if (StringUtils.isBlank(msg) || msg.contains("exception")) {
            msg = ErrorEnums.ILLEGAL_PARAM.getMsg();
        } else if (NOT_EMPTY.equals(msg)) {
            msg = ErrorEnums.ILLEGAL_LESS_PARAM_TEMPLETE.format(error.getField()).getSvrMsg();
        }
        return BaseResult.failure(ErrorEnums.ILLEGAL_PARAM.getCode(), msg);
    }

    private ErrorEnums handle(Throwable e) {
        ErrorEnums error = ErrorEnums.UNKNOWN_ERROR;
        if (e instanceof MulongException) {
            error = ((MulongException) e).getError();
        }
        String exceptionName = e.getClass().getName();
        if (exceptionName.contains(".sql.")) {
            error = ErrorEnums.SYSTEM_ERROR;
        }
        if (exceptionName.contains(".http.") || exceptionName.contains(".web.")) {
            error = ErrorEnums.ILLEGAL_REQUEST;
        }
        return error;
    }

    private void logResponse(Object res) {
        if (res == null) {
            return;
        }
        long beginTime = RequestContext.getBeginTime();
        long useTime = System.currentTimeMillis() - beginTime;
        log.info("Response << time: {} - data: {}", useTime, JSON.toJSONString(res));
    }

}
